<?php
/* SVN FILE: $Id: lil_blogs_spam_plugin_controller.php 184 2009-10-21 18:52:07Z miha@nahtigal.com $ */
/**
 * Short description for lil_blogs_spam_plugin_controller.php
 *
 * Long description for lil_blogs_spam_plugin_controller.php
 *
 * PHP versions 4 and 5
 *
 * Copyright (c) 2009, Miha Nahtigal
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright     Copyright (c) 2009, Miha Nahtigal
 * @link          http://www.nahtigal.com/
 * @package       lil_blogs_spam
 * @subpackage    lil_blogs_spam
 * @since         v 1.0
 * @version       $Revision: 184 $
 * @modifiedby    $LastChangedBy: miha@nahtigal.com $
 * @lastmodified  $Date: 2009-10-21 18:52:07 +0000 (Wed, 21 Oct 2009) $
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */
define('LILCOMMENT_SPAM', -2);
App::import('Controller', 'LilBlogs.LilBlogsPlugin');
/**
 * LilBlogsSpamPlugin class
 *
 * @uses          LilBlogsSpamPluginController
 * @package       lil_blogs_spam
 * @subpackage    lil_blogs_spam
 */
class LilBlogsSpamPluginController extends LilBlogsPluginController {
/**
 * name property
 *
 * @var string 'LilBlogsSpamPlugin'
 * @access public
 */
	var $name = 'LilBlogsSpamPlugin';
/**
 * initialize method
 *
 * This is a plugins initialization method
 *
 * @access public
 * @return void
 */
	function _initialize() {
	    require_once dirname(__FILE__).DS.'..'.DS.'config'.DS.'core.php';
	    
		// controller callbacks
		$this->attachHandler('admin_comments_index_conditions', '_filterAdminComments');
		
		// model callbacks
		$this->attachHandler('before_save_comment', '_setCommentStatus');
		
		// view callbacks
		$this->attachHandler('form_edit_comment', '_addSpamStatusToForm');
		$this->attachHandler('admin_index_comments', '_addSpamStatusToIndexTable');
		
		
		// other callbacks
		$this->attachHandler('admin_sidebar', '_setAdminSidebar');
		$this->attachHandler('before_comments_admin_quick', '_setAdminBulkStatus');
	}

/**
 * filterAdminComments method
 *
 * Adds spam conditions to filter
 *
 * @param mixed $controller
 * @param mixed $data
 * @access public
 * @return void
 */
	function _filterAdminComments($controller, $args) {
		if (@$controller->params['named']['status'] == 'spam') {
			$args['conditions']['Comment.status'] = LILCOMMENT_SPAM;
		}
		return $args;
	}
/**
 * setCommentStatus method
 *
 * This function sets status field in comment record.
 *
 * @param mixed $model
 * @param mixed $data
 * @access public
 * @return void
 */
	function _setCommentStatus($model, $args) {
		switch (strtolower(Configure::read('LilBlogsSpam.filter'))) {
			case 'bayes':
				App::import('Vendor', 'LilBlogsSpam.NaiveBayesian');
				App::import('Model', 'LilBlogsSpam.NbCategory');
				$nbs = new NbCategory();
				$bayes = new NaiveBayesian($nbs);
				
				$result = $bayes->categorize(
				    $args['Comment']['author'] . PHP_EOL .
				    $args['Comment']['email'] . PHP_EOL .
				    $args['Comment']['url'] . PHP_EOL .
					$args['Comment']['body']
				);
				unset($bayes);
				
				asort($result, SORT_NUMERIC);
				$result = array_reverse($result, true);
				$probability = reset($result);
		        if (abs($probability - 0.5) < 0.25) {
		        	$args['Comment']['status'] = LILCOMMENT_PENDING;
		        } else {
					$cat = reset(array_keys($result));
					$result = array('spam' => LILCOMMENT_SPAM, 'ham' => LILCOMMENT_APPROVED);
					$args['Comment']['status'] = $result[$cat];
				}
				break;
			case 'akismet':
				if (!$akismet_url = Configure::read('LilBlogsSpam.akismetUrl')) $akismet_url = Router::url('/', true);
				if ($akismet_key = Configure::read('LilBlogsSpam.akismetKey')) {
					App::import('Vendor', 'LilBlogsSpam.Akismet', array('file' => 'Akismet.class.php'));
					$akismet = new Akismet($akismet_url, $akismet_key);
					if ($akismet->isKeyValid()) {
						$post = $model->Post->find('first', array(
							'conditions' => array('Post.id' => $args['Comment']['post_id']),
							'fields'     => array('blog_id', 'slug')
						));
						$akismet->setCommentAuthor($args['Comment']['author']);
						$akismet->setCommentAuthorEmail($args['Comment']['email']);
						$akismet->setCommentAuthorURL($args['Comment']['url']);
						$akismet->setCommentContent($args['Comment']['body']);
						
						App::import('Helper', 'Html');
						$html = new HtmlHelper();
						$akismet->setPermalink($html->url(array(
							'admin'      => false,
							'plugin'     => 'lil_blogs',
							'controller' => 'posts',
							'action'     => 'view',
							'blogname'   => $model->Post->Blog->field('short_name', array('Blog.id' => $post['Post']['blog_id'])),
							'post'       => $post['Post']['slug']
						)));
						unset($html);
						
						if ($akismet->isCommentSpam()) {
							$args['Comment']['status'] = LILCOMMENT_SPAM;
						} else {
							$args['Comment']['status'] = LILCOMMENT_APPROVED;
						}
						// beware! if no akismet key present, this will fallback to Snook
						break;
					}
					unset($akismet);
				}
			default:
				$args['Comment']['status'] = $this->__categorizeSnook($args['Comment']);
		}
		return $args;
	}
/**
 * addSpamStatusToForm method
 *
 * This function adds spam status to drop down when editing comment
 *
 * @param mixed $view
 * @param mixed $data
 * @access public
 * @return void
 */
	function _addSpamStatusToForm($view, $args) {
		$args['form']['lines']['status']['parameters']['options']['options'][LILCOMMENT_SPAM] = 
			__d('lil_blogs_spam', 'Spam', true);
		return $args;
	}
/**
 * addSpamStatusToIndexTable method
 *
 * This function adds spam status to table in comments/index action
 *
 * @param mixed $view
 * @param mixed $data
 * @access public
 * @return void
 */
	function _addSpamStatusToIndexTable($view, $args) {
		// following line adds bulk action "Mark as spam"
		$args['actions']['lines']['action']['parameters']['params']['options']['spam'] =
			__d('lil_blogs_spam', 'Mark as Spam', true);
	
		// this line adds spam status to table
		foreach ($args['table']['element']['body']['rows'] as &$row) {
			if (@$row['comment']['Comment']['status'] == LILCOMMENT_SPAM) {
				$row['columns']['status']['html'] = __d('lil_blogs_spam', 'Spam', true);
			}
		}
		return $args;
	}
/**
 * setAdminSidebar method
 *
 * This function modify admin sidebar
 *
 * @param mixed $controller
 * @param array $sidebar
 * @access public
 * @return array
 */
	function _setAdminSidebar($controller, $sidebar) {
		$sidebar['common']['items']['comments']['submenu']['spam'] = array(
			'visible' => true,
			'title' => !isset($controller->Comment) ? __d('lil_blogs_spam', 'Spam', true) :
				sprintf(__d('lil_blogs_spam', 'Spam (%d)', true), $controller->Comment->find('count', array(
				'conditions' => array_merge(
					empty($controller->params['named']['post_id']) ? 
						array() : 
						array('Comment.post_id' => $controller->params['named']['post_id']),
					array(
						'Post.blog_id' => $controller->params['admin.blog_id'],
						'Comment.status' => LILCOMMENT_SPAM
					)
				),
				'contain' => array('Post'),
			))),
			'url' => array_merge(
				array(
					'plugin' => 'lil_blogs',
					'prefix' => 'admin',
					'controller' => 'comments',
					'action' => 'index',
					'status' => 'spam'
				),
				empty($this->params['named']['post_id']) ? array() : array('post_id' => $this->params['named']['post_id'])
			),
			'params' => array(),
			'active' => (isset($controller->params['named']['status']) && ($controller->params['named']['status'] == 'spam'))
		);
		return $sidebar;
	}
/**
 * setAdminBulkStatus method
 *
 * Set status of selected comments to spam
 *
 * @param mixed $controller
 * @param array $sidebar
 * @access public
 * @return array
 */
	function _setAdminBulkStatus($controller, $args) {
		if (!empty($controller->data['Comment']['comments']) &&
		!empty($controller->data['Comment']['action']) &&
		($controller->data['Comment']['action'] == 'spam'))
		{
			foreach ((array)$controller->data['Comment']['comments'] as $comment_id) {
				$controller->Comment->id = $comment_id;
				if (!empty($comment_id) && $controller->Comment->exists(true)) {
					if (!$controller->Comment->saveField('status', LILCOMMENT_SPAM)) {
						$controller->Session->setFlash(__d('lil_blogs_spam', 'Ups, an error occured. Returning back.', true), 'error');
						$controller->redirect($controller->referer());
						exit;
					}
				}
			}
			$controller->Session->setFlash(__d('lil_blogs_spam', 'Comments have been marked as spam.', true));
			$controller->redirect($controller->referer());
			exit;
		}
	}
/**
 * __categorizeSnook method
 *
 * This function categorizes comment using Snook's (www.snook.ca) algorithm.
 *
 * @param mixed $c Comment
 * @access public
 * @return int
 */
	function __categorizeSnook($c) {
		$score = 0;
		
		App::import('model', 'LilBlogs.Comment');
		$comments =& new Comment();
		
		// check for link frequency
		$count = substr_count(strtolower($c['body']), 'http://');
		if($count > 2) $score -= $count;
		if($count < 2) $score += 2;
		if($count == 0 && strlen($c['body'] > 20)) $score += 2;
		if( strlen($c['body'] < 20) ) $score -= 1;
		
		// check for email frequency
		$score += $comments->find('count', array('conditions' => array('Comment.email' => $c['email'], 'Comment.status' => LILCOMMENT_APPROVED)));
		$score -= $comments->find('count', array('conditions' => array('Comment.email' => $c['email'], 'Comment.status' => LILCOMMENT_SPAM)));
		$score -= $comments->find('count', array('conditions' => array('Comment.ip' => $c['ip'], 'Comment.status' => LILCOMMENT_SPAM)));
		// $score -= $comments->findCount( "DATE_ADD(created, INTERVAL 1 day) > now()" );
		
		// check for banned keyword frequency
		$s = strtolower($c['body'] . $c['author'] . $c['url']); // lowercase it so that string matching is more reliable
		$words = array('levitra','viagra','casino','plavix','cialis','ativan','fioricet','rape','acyclovir', 'penis','phentermine','porno','pharm','ringtone','pharmacy','url>');
		foreach($words AS $word) {
			if( strpos($s, $word) !== false ) $score--;
		}
		
		// check for bad URL signs
		$s = strtolower($c['url']); // lowercase it so that string matching is more reliable
		$words = array('.html', '.info', '?', '&', 'free');
		foreach($words AS $word) {
			if( strpos($s, $word) !== false ) $score--;
		}
		
		$score -= preg_match('/\.(de|pl|cn)(\/|$)/', $c['url']) ? 2 : 0; //check for .de or .pl domains since they tend to spam
		$score -= preg_match('/-.*-.*htm$/', $c['url']) ? 2 : 0; // mark this -2 because it's always spam
		$score -= (strlen($c['url']) > 30 ? 1 : 0 ); //spam urls are on average 38 chars long
		
		$regexs = array('/^interesting\r\n$/','/^Interesting...\r\n$/','/^Sorry \:\(\r\n$/','/^Nice(!|\.)*\r\n$/','/^Cool(!|\.)*\r\n$/');
		foreach($regexs AS $regex) {
			if( preg_match($regex, $c['body']) ) $score -= 10;
		}
		
		// check for spam phrases
		//      $badphrases = array('Hi! Very nice site! Thanks you very much!');
		//      foreach($badphrases AS $phrase)
		//      {
		//        similar_text($phrase, $c['body'], $percent);
		//        if($percent > 50) $score -= round($percent / 10);
		//      }
		
		// check if URL is in author
		$count = substr_count (strtolower($c['author']), 'http://');
		$score -= ($count * 2);
		
		// check if body has been used before
		$score -= $comments->find('count', array('conditions' => array("Comment.body" => $c['body'])));
		
		// check if we're coming up with nonsense words
		preg_match_all('/[bcdfghjklmnpqrstvwxz]{5}/', strtolower($c['email'].$c['author']), $m);
		$score -= count($m[0]);
		
		return ($score < 0) ? LILCOMMENT_SPAM : (($score == 0) ? LILCOMMENT_PENDING : LILCOMMENT_APPROVED);
	}
}
?>